/******************************************************************************
 * M5Snake : Game board management                                            *
 * -------------------------------                                            *
 * Manage the game board (storage in memory and display                       *
 * Author: Olivier Staquet                                                    *
 * Last version available on https://github.com/ostaquet/M5Snake              *
 *****************************************************************************/
#include "GameBoard.h"

/**
 * Initialize
 */
void GameBoardClass::begin(uint8_t _max_game_cycles) {
    // Keep the number of game cycles
    max_game_cycles    = _max_game_cycles;
    current_game_cycle = 0;

    // Init the board at blank
    for (uint8_t x = 0; x < board_width; x++) {
        for (uint8_t y = 0; y < board_height; y++) {
            board_data[x][y]    = BLOCK_STATUS_EMPTY;
            board_changes[x][y] = 0;
        }
    }

    // Set screen to blank
    M5.Lcd.fillScreen(BLACK);
}

/**
 * Refresh display
 */
void GameBoardClass::refresh() {
    // Check where there are some changes
    for (uint8_t x = 0; x < board_width; x++) {
        for (uint8_t y = 0; y < board_height; y++) {
            // Check the cell
            if (board_changes[x][y] > 0) {
                // There is a change...
                drawChange(x, y);
                // Reset the change tracking
                board_changes[x][y] = 0;
            }
        }
    }
}

/**
 * Put the head of the snake on the board and go on right
 */
void GameBoardClass::startSnake() {
    // Define the middle of the screen
    setCell(board_width / 2, board_height / 2, BLOCK_STATUS_HEAD);

    // Define the direction
    setDirection(DIRECTION_RIGHT);
}

/**
 * Make the snake move on the board
 * Return boolean true if OK, false if game over
 */
bool GameBoardClass::moveSnake() {
    // Check if it is a cycle to move
    if (current_game_cycle < max_game_cycles) {
        // Wait for the next cycle
        current_game_cycle++;
        return true;
    } else {
        // Reset the game cycle
        current_game_cycle = 0;
    }

    // Add 1 to all current block with between 1 and 512
    // to keep count of the movement of the snake (1 = head, 2 = 2nd block after
    // head...)
    for (uint8_t x = 0; x < board_width; x++) {
        for (uint8_t y = 0; y < board_height; y++) {
            if (board_data[x][y] < BLOCK_STATUS_CHERRY &&
                board_data[x][y] != BLOCK_STATUS_EMPTY) {
                board_data[x][y] = board_data[x][y] + 1;
            }
        }
    }

    // Next move to be defined
    int8_t next_block_x = current_head_x;
    int8_t next_block_y = current_head_y;

    // Define the next move of the head
    switch (current_direction) {
        case DIRECTION_UP:
            next_block_y = current_head_y - 1;
            break;
        case DIRECTION_RIGHT:
            next_block_x = current_head_x + 1;
            break;
        case DIRECTION_DOWN:
            next_block_y = current_head_y + 1;
            break;
        case DIRECTION_LEFT:
            next_block_x = current_head_x - 1;
            break;
    }

    // Check if the move is valid...
    // Check the limit of the board for X
    if (next_block_x < 0 || next_block_x >= board_width) {
        return false;
    }
    // Check the limit of the board for Y
    if (next_block_y < 0 || next_block_y >= board_height) {
        return false;
    }

    // Check if there is a cherry on the cell (if not a cherry, remove the last
    // block of the tail
    if (board_data[next_block_x][next_block_y] != BLOCK_STATUS_CHERRY) {
        removeTail();
    }

    // Check if there is another part of the snake
    if (board_data[next_block_x][next_block_y] > BLOCK_STATUS_EMPTY &&
        board_data[next_block_x][next_block_y] < BLOCK_STATUS_CHERRY) {
        return false;
    }

    // OK, move the head of the snake
    setCell(next_block_x, next_block_y, BLOCK_STATUS_HEAD);

    return true;
}

/**
 * Identify and remove the tail (last block of the snake)
 */
void GameBoardClass::removeTail() {
    uint16_t greatest_value = 0;
    uint8_t tail_x          = 0;
    uint8_t tail_y          = 0;

    // Find the cell with the biggest value (it is the tail)
    for (uint8_t x = 0; x < board_width; x++) {
        for (uint8_t y = 0; y < board_height; y++) {
            if (board_data[x][y] < BLOCK_STATUS_CHERRY) {
                if (board_data[x][y] > greatest_value) {
                    tail_x         = x;
                    tail_y         = y;
                    greatest_value = board_data[x][y];
                }
            }
        }
    }

    // Remove the tail
    setCell(tail_x, tail_y, BLOCK_STATUS_EMPTY);
}

/**
 * Get the max score
 */
uint16_t GameBoardClass::getMaxScore() {
    uint16_t greatest_value = 0;

    // Find the cell with the biggest value (it is the tail)
    for (uint8_t x = 0; x < board_width; x++) {
        for (uint8_t y = 0; y < board_height; y++) {
            if (board_data[x][y] < BLOCK_STATUS_CHERRY) {
                if (board_data[x][y] > greatest_value) {
                    greatest_value = board_data[x][y];
                }
            }
        }
    }

    return greatest_value - 1;
}

/**
 * Draw the change of one cell
 */
void GameBoardClass::drawChange(uint8_t x, uint8_t y) {
    // Compute box position
    uint16_t pos_x = x * BLOCK_SIZE;
    uint16_t pos_y = y * BLOCK_SIZE;

    // Depending on the content of the cell, draw the box
    switch (board_data[x][y]) {
        case BLOCK_STATUS_EMPTY:
            M5.Lcd.fillRect(pos_x, pos_y, BLOCK_SIZE, BLOCK_SIZE, BLACK);
            break;

        case BLOCK_STATUS_CHERRY:
            M5.Lcd.fillRect(pos_x, pos_y, BLOCK_SIZE, BLOCK_SIZE, BLACK);
            M5.Lcd.fillCircle(pos_x + BLOCK_SIZE / 2, pos_y + BLOCK_SIZE / 2,
                              BLOCK_SIZE / 2 - 1, RED);
            break;

        default:
            M5.Lcd.drawRect(pos_x, pos_y, BLOCK_SIZE, BLOCK_SIZE, BLACK);
            M5.Lcd.fillRect(pos_x + 1, pos_y + 1, BLOCK_SIZE - 2,
                            BLOCK_SIZE - 2, WHITE);
            break;
    }
}

/**
 * Add a ramdom cherry on the board
 */
void GameBoardClass::addCherry() {
    uint8_t pos_x = random(0, board_width);
    uint8_t pos_y = random(0, board_height);

    while (board_data[pos_x][pos_y] != BLOCK_STATUS_EMPTY) {
        pos_x = random(0, board_width);
        pos_y = random(0, board_height);
    }

    setCell(pos_x, pos_y, BLOCK_STATUS_CHERRY);
}

/**
 * Set direction
 */
void GameBoardClass::setDirection(uint8_t direction) {
    current_direction = direction;
}

/**
 * Set a value in a cell
 */
void GameBoardClass::setCell(uint8_t x, uint8_t y, uint16_t status) {
    board_data[x][y]    = status;
    board_changes[x][y] = 1;

    if (status == BLOCK_STATUS_HEAD) {
        current_head_x = x;
        current_head_y = y;
    }
}

GameBoardClass GameBoard;
